#include <sdk.h>
#include <wx/tokenzr.h>
#include "DoxyBlocks.h"
#include "Expressions.h"

void DoxyBlocks::OnBlockComment( wxCommandEvent & ( event ) ) {
  DoBlockComment();
}

void DoxyBlocks::DoBlockComment() {
  if( !IsProjectOpen() ) {
    return;
  }
  AppendToLog( _( "Writing block comment..." ) );
  int iBlockComment = m_pConfig->GetBlockComment();
  bool bUseAtInTags = m_pConfig->GetUseAtInTags();
  M_Code_Editor *cbEd = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
  if( !cbEd ) {
    return;
  }
  cbStyledTextCtrl *control = cbEd->GetControl();
  wxInt32 line = control->GetCurrentLine();
  int iPos = control->PositionFromLine( line );
  int iIndent = control->GetLineIndentation( line );
  wxString sIndent( wxT( ' ' ), iIndent );
  wxString sTagChar;
  sTagChar = bUseAtInTags ? wxT( "@" ) : wxT( "\\" );
  wxString sSpace( wxT( " " ) );
  wxString sTagBrief( sSpace + sTagChar + wxT( "brief" ) );
  wxString sTagParam( sSpace + sTagChar + wxT( "param" ) );
  wxString sTagReturn( sSpace + sTagChar + wxT( "return" ) );
  wxString sStartComment;
  wxString sMidComment;
  wxString sEndComment;
  GetBlockCommentStrings( iBlockComment, sStartComment, sMidComment, sEndComment );
  wxString sLine = control->GetLine( control->GetCurrentLine() );
  control->BeginUndoAction();
  if( IsLanguageFortran( cbEd ) ) {
    sStartComment = wxT( "!>" );
    sMidComment = wxT( "!!" );
    sEndComment = wxT( "!!" );
    StartComment( control, iPos, iBlockComment, sStartComment, sMidComment, sTagBrief, sIndent );
    CommentFortran( control, line + 1, iPos, sMidComment, sTagParam, sTagReturn, sIndent );
    AddCommentLine( control, iPos, sIndent + sEndComment );
  } else if( reClass.Matches( sLine ) ) {
    StartComment( control, iPos, iBlockComment, sStartComment, sMidComment, sTagBrief, sIndent );
    AddCommentLine( control, iPos, sIndent + sEndComment );
  } else if( reStruct.Matches( sLine ) ) {
    StartComment( control, iPos, iBlockComment, sStartComment, sMidComment, sTagBrief, sIndent );
    AddCommentLine( control, iPos, sIndent + sEndComment );
  } else if( reTypedef.Matches( sLine ) ) {
    StartComment( control, iPos, iBlockComment, sStartComment, sMidComment, sTagBrief, sIndent );
    AddCommentLine( control, iPos, sIndent + sEndComment );
  } else if( reEnum.Matches( sLine ) ) {
    StartComment( control, iPos, iBlockComment, sStartComment, sMidComment, sTagBrief, sIndent );
    AddCommentLine( control, iPos, sIndent + sEndComment );
  } else if( reClassFunctionNoRet.Matches( sLine ) ) {
    StartComment( control, iPos, iBlockComment, sStartComment, sMidComment, sTagBrief, sIndent );
    wxString sParams = reClassFunctionNoRet.GetMatch( sLine, 3 );
    CommentFunction( control, iPos, sMidComment, sTagParam, sTagReturn, sIndent, sParams, wxEmptyString, wxEmptyString );
    if( !sParams.IsEmpty() ) {
      AddCommentLine( control, iPos, sIndent + sMidComment );
    }
    AddCommentLine( control, iPos, sIndent + sEndComment );
  } else if( reClassFunction.Matches( sLine ) ) {
    StartComment( control, iPos, iBlockComment, sStartComment, sMidComment, sTagBrief, sIndent );
    CommentFunction( control, iPos, sMidComment, sTagParam, sTagReturn, sIndent, reClassFunction.GetMatch( sLine, 6 ), reClassFunction.GetMatch( sLine, 1 ), reClassFunction.GetMatch( sLine, 4 ) );
    AddCommentLine( control, iPos, sIndent + sMidComment );
    AddCommentLine( control, iPos, sIndent + sEndComment );
  } else if( reFunction.Matches( sLine ) ) {
    StartComment( control, iPos, iBlockComment, sStartComment, sMidComment, sTagBrief, sIndent );
    CommentFunction( control, iPos, sMidComment, sTagParam, sTagReturn, sIndent, reFunction.GetMatch( sLine, 5 ), reFunction.GetMatch( sLine, 1 ), reFunction.GetMatch( sLine, 4 ) );
    AddCommentLine( control, iPos, sIndent + sMidComment );
    AddCommentLine( control, iPos, sIndent + sEndComment );
  } else {
    StartComment( control, iPos, iBlockComment, sStartComment, sMidComment, sTagBrief, sIndent );
    AddCommentLine( control, iPos, sIndent + sMidComment );
    AddCommentLine( control, iPos, sIndent + sMidComment + sTagParam + sSpace );
    AddCommentLine( control, iPos, sIndent + sMidComment + sTagParam + sSpace );
    AddCommentLine( control, iPos, sIndent + sMidComment + sTagReturn + sSpace );
    AddCommentLine( control, iPos, sIndent + sMidComment );
    AddCommentLine( control, iPos, sIndent + sEndComment );
  }
  if( iBlockComment == 4 || iBlockComment == 5 ) {
    line += 1;
  }
  iPos = control->GetLineEndPosition( line );
  control->GotoPos( iPos );
  control->EndUndoAction();
}

void DoxyBlocks::OnLineComment( wxCommandEvent & ( event ) ) {
  DoLineComment();
}

void DoxyBlocks::DoLineComment() {
  if( !IsProjectOpen() ) {
    return;
  }
  AppendToLog( _( "Writing line comment..." ) );
  int iLineComment = m_pConfig->GetLineComment();
  M_Code_Editor *cbEd = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
  if( !cbEd ) {
    return;
  }
  cbStyledTextCtrl *control = cbEd->GetControl();
  int iPos = control->GetCurrentPos();
  wxString sComment;
  switch( iLineComment ) {
    case 0:
      sComment = wxT( "/**<  */" );
      break;
    case 1:
      sComment = wxT( "//!< " );
      break;
    case 2:
      sComment = wxT( "///< " );
      break;
    case 3:
      sComment = wxT( "/*!<  */" );
      break;
    default:
      break;
  }
  int iMax = 5;
  if( IsLanguageFortran( cbEd ) ) {
    sComment = wxT( "!< " );
    iMax = 3;
  }
  control->BeginUndoAction();
  control->InsertText( iPos, sComment );
  int i = 0;
  while( i < iMax ) {
    control->CharRight();
    i++;
  }
  control->EndUndoAction();
}

void DoxyBlocks::GetBlockCommentStrings( int iBlockComment, wxString &sStartComment, wxString &sMidComment, wxString &sEndComment ) {
  switch( iBlockComment ) {
    case 0:
      sStartComment = wxT( "/**" );
      sMidComment = wxT( " *" );
      sEndComment = wxT( " */" );
      break;
    case 1:
      sStartComment = wxT( "//!" );
      sMidComment = wxT( "//!" );
      sEndComment = wxT( "//!" );
      break;
    case 2:
      sStartComment = wxT( "///" );
      sMidComment = wxT( "///" );
      sEndComment = wxT( "///" );
      break;
    case 3:
      sStartComment = wxT( "/*!" );
      sMidComment = wxT( " *" );
      sEndComment = wxT( " */" );
      break;
    case 4:
      sStartComment = wxT( "/********************************************//**" );
      sMidComment = wxT( " *" );
      sEndComment = wxT( " ***********************************************/" );
      break;
    case 5:
      sStartComment = wxT( "/////////////////////////////////////////////////" );
      sMidComment = wxT( "///" );
      sEndComment = wxT( "/////////////////////////////////////////////////" );
      break;
    default:
      break;
  }
}

void DoxyBlocks::StartComment( cbStyledTextCtrl *control, int &iPos, int iBlockComment, wxString sStartComment, wxString sMidComment, wxString sTagBrief, wxString sIndent ) {
  wxString sSpace( wxT( " " ) );
  control->GotoPos( iPos );
  control->NewLine();
  control->LineUp();
  if( iBlockComment == 4 || iBlockComment == 5 ) {
    control->AddText( sIndent + sStartComment );
    control->NewLine();
    iPos = control->PositionFromLine( control->GetCurrentLine() );
    control->AddText( sIndent + sMidComment + sTagBrief + sSpace );
  } else {
    control->AddText( sIndent + sStartComment + sTagBrief + sSpace );
  }
}

void DoxyBlocks::CommentFunction( cbStyledTextCtrl * control, int &iPos, wxString sMidComment, wxString sTagParam, wxString sTagReturn, wxString sIndent, wxString sParams, wxString sReturn, wxString sFunction ) {
  wxString sSpace( wxT( " " ) );
  if( !sParams.IsEmpty() || !sReturn.IsEmpty() ) {
    AddCommentLine( control, iPos, sIndent + sMidComment );
  }
  wxStringTokenizer tokenizer( sParams, wxT( "," ) );
  while( tokenizer.HasMoreTokens() ) {
    wxString sParam = tokenizer.GetNextToken();
    sParam.Trim();
    sParam.Trim( false );
    wxStringTokenizer tokenizerParam( sParam, wxT( " " ) );
    wxArrayString arElements;
    while( tokenizerParam.HasMoreTokens() ) {
      wxString sElement = tokenizerParam.GetNextToken();
      arElements.Add( sElement );
    }
    if( arElements.GetCount() == 4 ) {
      sParam = arElements[3] + sSpace + arElements[0] + sSpace + arElements[1] + arElements[2];
    } else if( arElements.GetCount() == 3 ) {
      if( arElements[1] == wxT( "*" ) || arElements[1] == wxT( "&" ) || arElements[1].StartsWith( wxT( "**" ) ) ) {
        sParam = arElements[2] + sSpace + arElements[0] + arElements[1];
      } else {
        wxString sRightFirst = arElements[2].Left( 1 );
        wxString sLeftLast = arElements[1].Right( 1 );
        if( sRightFirst == wxT( "*" ) || sRightFirst  == wxT( "&" ) ) {
          if( arElements[2].StartsWith( wxT( "**" ) ) ) {
            arElements[2].Remove( 0, 2 );
            sParam = arElements[2] + sSpace + arElements[0] + sSpace + arElements[1] + wxT( "**" );
          } else {
            arElements[2].Remove( 0, 1 );
            sParam = arElements[2] + sSpace + arElements[0] + sSpace + arElements[1] + sRightFirst;
          }
        } else if( sLeftLast == wxT( "*" ) || sLeftLast  == wxT( "&" ) ) {
          arElements[1].RemoveLast();
          sParam = arElements[2] + sSpace + arElements[0] + sSpace + arElements[1] + sLeftLast;
        } else
        { sParam = arElements[2] + sSpace + arElements[0] + sSpace + arElements[1]; }
      }
    } else if( arElements.GetCount() == 2 ) {
      wxString sRightFirst = arElements[1].Left( 1 );
      wxString sLeftLast = arElements[0].Right( 1 );
      if( sRightFirst == wxT( "*" ) || sRightFirst  == wxT( "&" ) ) {
        if( arElements[1].StartsWith( wxT( "**" ) ) ) {
          arElements[1].Remove( 0, 2 );
          sParam = arElements[1] + sSpace + arElements[0] + wxT( "**" );
        } else {
          arElements[1].Remove( 0, 1 );
          sParam = arElements[1] + sSpace + arElements[0] + sRightFirst;
        }
      } else if( sLeftLast == wxT( "*" ) || sLeftLast  == wxT( "&" ) ) {
        arElements[0].RemoveLast();
        sParam = arElements[1] + sSpace + arElements[0] + sLeftLast;
      } else
      { sParam = arElements[1] + sSpace + arElements[0]; }
    }
    sParam.Replace( wxT( "/*" ), wxT( "" ), true );
    sParam.Replace( wxT( "*/" ), wxT( "" ), true );
    AddCommentLine( control, iPos, sIndent + sMidComment + sTagParam + sSpace + sParam );
  }
  sReturn = ProcessReturnString( sReturn, sFunction );
  if( !sReturn.IsEmpty() ) {
    AddCommentLine( control, iPos, sIndent + sMidComment + sTagReturn + sSpace + sReturn );
  }
}

void DoxyBlocks::AddCommentLine( cbStyledTextCtrl *control, int& iPos, wxString sText ) {
  control->NewLine();
  iPos = control->PositionFromLine( control->GetCurrentLine() );
  control->GotoPos( iPos );
  control->AddText( sText );
}

wxString DoxyBlocks::ProcessReturnString( wxString sReturn, wxString sFunction ) {
  if( sReturn.Contains( wxT( "static" ) ) ) {
    sReturn.Replace( wxT( "static" ), wxT( "" ) );
  }
  if( sReturn.Contains( wxT( "inline" ) ) ) {
    sReturn.Replace( wxT( "inline" ), wxT( "" ) );
  }
  sReturn.Trim( false );
  if( !sReturn.IsEmpty() ) {
    wxString sFuncFirst = sFunction.Left( 1 );
    if( sFuncFirst == wxT( "*" ) || sFuncFirst == wxT( "&" ) ) {
      if( sFunction.StartsWith( wxT( "**" ) ) ) {
        sReturn += wxT( "**" );
        sFunction.Remove( 0, 2 );
      } else {
        sReturn += sFuncFirst;
        sFunction.Remove( 0, 1 );
      }
    }
    sReturn.Trim();
    int l = sReturn.Length();
    int i = sReturn.rfind( ' ', l );
    if( i == l - 2 || i == l - 3 ) {
      wxString sTemp = sReturn.BeforeLast( ' ' ) + sReturn.AfterLast( ' ' );
      sReturn = sTemp;
    }
  }
  return sReturn;
}

void DoxyBlocks::CommentFortran( cbStyledTextCtrl *control, int iLine, int &iPos, wxString sMidComment, wxString sTagParam, wxString sTagReturn, wxString sIndent ) {
  wxString sSpace( wxT( " " ) );
  wxString sLine = control->GetLine( iLine );
  sLine = sLine.BeforeFirst( '!' );
  wxString sLineLw = sSpace + sLine.Lower().Trim( false );
  if( ( sLineLw.Find( _T( " function " ) )   == wxNOT_FOUND ) && ( sLineLw.Find( _T( " subroutine " ) ) == wxNOT_FOUND ) ) {
    return;
  }
  while( sLine.Trim().EndsWith( _T( "&" ) ) ) {
    iLine++;
    wxString contLine = control->GetLine( iLine );
    if( contLine.IsEmpty() ) {
      break;
    }
    sLine.Append( contLine.BeforeFirst( '!' ).Trim().Trim( false ) );
  }
  sLine.Replace( _T( "&&" ), wxEmptyString );
  bool isFunction = false;
  int idxW = sLineLw.Find( _T( " function " ) );
  if( idxW == wxNOT_FOUND ) {
    idxW = sLineLw.Find( _T( " subroutine " ) );
  } else {
    isFunction = true;
  }
  if( idxW == wxNOT_FOUND ) {
    return;
  }
  wxString sLineShort = sLine.Mid( idxW );
  int idxParStart = sLineShort.Find( _T( "(" ) );
  int idxParEnd = sLineShort.Find( _T( ")" ) );
  if( idxParStart == wxNOT_FOUND || idxParEnd == wxNOT_FOUND ) {
    return;
  }
  wxString sParams = sLineShort.Mid( idxParStart + 1, idxParEnd - idxParStart - 1 );
  AddCommentLine( control, iPos, sIndent + sMidComment );
  wxStringTokenizer tokenizerParam( sParams, wxT( " \t\r\n&," ), wxTOKEN_STRTOK );
  while( tokenizerParam.HasMoreTokens() ) {
    wxString sParam = tokenizerParam.GetNextToken();
    AddCommentLine( control, iPos, sIndent + sMidComment + sTagParam + sSpace + sParam );
  }
  if( isFunction ) {
    AddCommentLine( control, iPos, sIndent + sMidComment + sTagReturn + sSpace );
  }
}


bool DoxyBlocks::IsLanguageFortran( M_Code_Editor *cbEd ) {
  if( !cbEd ) {
    return false;
  }
  EditorColourSet* colour_set = cbEd->GetColourSet();
  if( colour_set ) {
    wxString strLang = colour_set->GetLanguageName( cbEd->GetLanguage() );
    if( strLang == _T( "Fortran" ) || strLang == _T( "Fortran77" ) ) {
      return true;
    }
  }
  return false;
}
